feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field - #2314
Merged
Merged
Conversation
…s via the manifest systemPrompt field
🦋 Changeset detectedLatest commit: 11c475e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
…gin contributions at session scope - restore no longer re-renders or re-persists prompts: a resumed agent keeps its replayed profile binding (prompt and tool set) as persisted - a new Session-level convergence point reloads plugin skills into the session skill catalog before fanning out to every live agent prompt, and every catalog-kind plugin mutation awaits the whole pipeline; MCP-only toggles carry a distinct change kind and skip it - live refreshes after a restart re-resolve the bound profile by name and rebind the full slice (prompt, disallowed tools, active tools) atomically, warning and keeping the persisted state when the profile is gone; renders reuse the first-render timestamp and unchanged prompts are not re-persisted, so convergence never churns the wire - cap plugin system-prompt contributions (32 KB per field/file, 64 KB aggregate per prompt build) with manifest diagnostics and warnings - bump the changeset to minor: this is a new user-facing capability
…ssing-profile warning - add sessionPluginContribution to the domain-layer registry so lint:domain stays green - emit system-prompt-refresh-profile-missing once per profile name, matching the service's other deduped warnings - document the convergence timeout escape hatch and the klient exclusion of enabledSystemPrompts
…ion read failures - emit plugin-sections-oversized once per skipped-plugin signature - let enabledSystemPrompts failures propagate to the refresh catch (keeps the current prompt and warns) instead of silently rendering and persisting a prompt without plugin instructions - cover the convergence timeout cut-off with a fake-timers test - clarify that the first-render timestamp anchors per process
…load timing - run at most one convergence per session and bound each change's wait by the timeout, so a fan-out emitter never interleaves deliveries after a timed-out convergence - fire onDidReload as soon as the reload commits again, keeping hook reloads independent of prompt convergence - sign the plugin budget warning with an unambiguous key
…mantics - the timeout retry promise only holds once stalled work clears - note the per-session serial delivery cost model on the plugin change contract and the dual-queue invariant on the service
…ompt template
- place ${plugin_sections} on the same template line as
${skills_section} so prompts without either block render exactly as
before this feature
- note on the change contract that waitUntil work must not call back
into plugin mutations, and spell out the per-session convergence
order in the user docs
…bind it
- applyBindingSnapshot left the fork with no pinned profile, which
routed in-process forks into the post-restart catalog rebind and
could reset an inherited tool set; forks now inherit the source
agent's pinned profile object
- pin the first-render timestamp reuse with a ${now}-embedding test
and document the anchored ${now} semantics
- tighten the plugin docs budget and resume-refresh wording
- an agent created while a plugin convergence is in flight now waits for it, and a restored agent refreshes once after it, so a plugin mutation never straddles an agent's bootstrap - warn on a non-string systemPrompt field and strip a UTF-8 BOM from systemPromptPath files before trimming - correct the consumption-surface wording (every CLI surface on the experimental flag, not just kimi -p), the per-session queueing note, and the single-plugin combined budget clause
A permanently wedged convergence kept convergeTail pending forever, and the unconditional settled() wait in bindBootstrap would have blocked every later agent creation in that session; the join now races the shared convergence timeout and continues (a restored agent still refreshes once, which never touches the tail), and the timeout constant moves to the contract for reuse
…stores - a convergence fan-out could land while an agent's wire log is still replaying, dispatching a replay-visible config record whose effect the rest of the replay then overwrites; refreshSystemPrompt now skips while the wire restore is in progress - convergence completion is tracked by a generation counter; bootstrap compares it (after a bounded join) and refreshes a restored agent exactly once when a round completed after its creation began, replacing the wasConverging flag that could miss both windows
…nnot stop the pipeline - the fan-out now races the convergence timeout, so convergeTail always settles: a permanently hung refresh delays its round (blocked entries drain oldest-first on later changes) instead of killing the session's convergence for good - warn when agent bootstrap stops waiting on a stalled convergence - diagnose a blank systemPromptPath and pin the plugin-root escape guard with traversal, absolute-path, and symlink tests
…ys, roll the prompt clock daily - the convergence's skill-reload segment now races the same timeout as the fan-out, so no segment of the pipeline can wedge a session for good; it continues with the previous catalog and retries next change - a cold rebind that resets the tool set replays session-added user tools onto the new base instead of dropping them for the rest of the process - the rendered timestamp re-anchors when the UTC date rolls over, so long-lived processes keep a fresh clock while steady-state renders stay byte-stable within a day - the plugin budget warning dedupes per plugin id, and the docs note that systemPromptPath content is frozen until the next reload
… drift-free gate - restore replays the persisted binding untouched, then bootstrap refreshes only when drift-free inputs changed while the session was cold: the catalog profile's tool set/denylist, or the plugin-sections baseline persisted alongside the prompt on the existing bind/update payloads; directory-listing and date drift wait for live triggers, so quiet resumes append no replay-visible records - the rendered timestamp is day-precision (UTC date at 00:00, re-anchored on rollover), keeping steady-state renders byte-stable across resumes and sessions on the same day - consolidate both timeout helpers onto a shared raceOutcome, and drop the generation counter the gate supersedes - align the plugin-sections precedence prose with the AGENTS.md disclaimer (no self-granted authority, system instructions win on conflict)
…ons baseline - the gate's plugin-sections read now races the convergence timeout, so agent creation never blocks behind an unrelated plugin mutation - refreshes serialize per agent through a tail, so overlapping triggers cannot write prompts out of order - when plugin sections change but a plugin-free custom prompt does not, the new baseline lands as a sections-only update instead of making every later resume re-render in vain - align the system prompt's Date and Time paragraph with the day-precision anchored timestamp
Live sessions pick up plugin changes, while the default TUI and `kimi -p` paths ignore these fields. Signed-off-by: 7Sageer <sag77r@hotmail.com>
Plugin mutations still converge live agent prompts, but the session skill catalog goes back to refreshing only on explicit plugin reload, as before: the prompt feature does not need skill convergence, and the pre-existing manual-reload semantics stay uniform across all plugin contributions. Removes the convergence-driven skill reload, the reloadSource de-privatization, and their tests; restores the PluginSkillSource onDidReload forwarding and its catalog tests.
…xplicit reload Drop the live convergence machinery (the plugin onDidChange barrier, the sessionPluginContribution fan-out, the restored-prompt drift gate, and the day-precision render clock) so plugin system-prompt sections take effect at the same point as every other plugin contribution: /plugins reload or a new session. The profile now refreshes when the session skill catalog re-pulls its plugin source on reload, reading both the skill list and the prompt sections fresh.
…ia the manifest systemPrompt field
Merged
Pidbid
added a commit
to Pidbid/kkm
that referenced
this pull request
Jul 30, 2026
* feat(agent-core): custom agent files and secondary model on the v1 engine (MoonshotAI#2232) * feat(agent-core): custom agent files and secondary model on the v1 engine Migrate the custom agentfile and secondary-model capabilities from agent-core-v2 to the v1 engine so they work in the TUI and plain kimi -p sessions: - discover Markdown agent files from user/project/extra/explicit directories with the v2 precedence rules, a merged session profile catalog replacing the hardcoded builtin profile lookups, SYSTEM.md main prompt override, and ${base_prompt} backed by the effective default - --agent/--agent-file now work in print mode on the default engine; CreateSessionOptions gains agentProfile/agentFiles - [secondary_model] config + KIMI_SECONDARY_MODEL/EFFORT bind newly spawned subagents to a cheaper model behind the secondary-model experiment flag, with primary/secondary model params on Agent and AgentSwarm and upfront session warnings - full disallowedTools deny semantics (exact names + mcp__ globs) evaluated by the tool manager and persisted in the agent wire * fix(cli): guard optional agentFiles in the prompt runner runPrompt is also driven programmatically (headless goal flow) with options that never pass through the CLI parser defaults, so agentFiles can be undefined; mirror the addDirs optional-chaining pattern. Also extend the SDK experimental-feature assertion with the secondary-model flag. * fix(agent-core): preserve custom agent bindings on v1 * fix(agent-core): narrow secondary model error hints * fix(agent-core): persist custom agent profile bindings * Delete .changeset/sdk-agent-profile-options.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * Update v1-custom-agent-files.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * Update v1-secondary-model.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * Update v1-custom-agent-files.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * fix(agent-core): keep SYSTEM.md a prompt-only overlay for delegation * docs: update agent file and secondary model availability wording * fix(cli): reject --agent-file combined with session resume The resume path only forwards the agent file's name for the bound-profile assertion; the file's content is never re-applied (the session keeps its creation-time catalog snapshot). Previously the combination was silently accepted, so an edited file (or a same-named one) appeared to apply but did not. Reject it at option validation and document the constraint. * refactor(agent-core): share prompt-section prose and note v2 twins in agentfile headers The Windows notes, additional-dirs and skills prose blocks existed twice: inline in the builtin default template (system.md) and as constants in the agent-file renderer (from-file.ts). Extract them to profile/prompt-sections.ts as the single source: system.md renders them through injected KIMI_* template variables and from-file.ts imports the same constants. Rendered prompts are byte-identical for all four builtin profiles across macOS/Windows and skills/dirs on/off; a new test pins system.md to the shared constants. Also mark each profile/agentfile file with the path of its agent-core-v2 counterpart so format/semantics changes land in both engines. * feat(cli): add /secondary_model command for the subagent model Mirror /model: a picker with a thinking-effort step that persists [secondary_model] and live-applies to the current session via a new Session.setSecondaryModel RPC (node-sdk wrapper included), so newly spawned subagents bind the new model right away. The /model picker now hides the synthesized __secondary__ derived entry; docs and the update-config builtin skill mention the section. * feat(tui): show the bound model in subagent run stats Subagents report their model alias via agent.status.updated after spawn; resolve it to a display name and surface it in tool-call subagent stats and agent-group rows. * fix(agent-core): validate agent profile before session persistence * fix(agent-core): refresh subagent tools after model switch * fix(agent-core): show subagent model preferences * fix(agent-core): preserve secondary model recipe on live apply * fix(agent-core): make secondary model apply explicit * fix(tui): refresh secondary model display state * chore: merge secondary model changesets into one * Add /secondary_model command for subagent configuration Show each subagent's model in the subagent card header and agent-group rows. Requires the secondary-model experiment (KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=1); run /secondary_model to pick a model and thinking effort, applied to the current session immediately. Signed-off-by: 7Sageer <sag77r@hotmail.com> * fix(agent-core): align explicit agent file precedence * fix(agent-core): let disallowedTools deny select_tools * chore(cli): drop engine mention from --agent/--agent-file help text * feat(cli): support --agent/--agent-file in the interactive TUI Bind the selected agent profile to the startup session when launching the TUI with --agent/--agent-file, including the session created after an OAuth login at startup. Sessions created later in the process (/new) keep the default profile. Make both flags creation-only in every mode: combining them with --session/--continue is now rejected in print mode too, since resume restores the bound agent from the session automatically. * fix(agent-core): persist new secondary-model selections under env overrides stripSecondaryModelConfig restored secondary_model.model/default_effort from raw whenever KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT was set, so a /secondary_model pick made under the env vars was silently discarded on write. Restore from raw only when the value being written still equals the env value (an overlay round-trip), mirroring the pointer check in stripEnvModelConfig; a genuinely different selection now reaches config.toml. * fix(cli): report the effective secondary model when env overrides the pick /secondary_model toasted the picked alias even when KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT made the session bind a different model. Read the effective binding back from the reloaded config (as /model does from session status) and warn with the env-overridden values instead. * feat(tui): show the bound model name in the AgentSwarm panel header --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> (cherry picked from commit efac96c) * fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2 (MoonshotAI#2345) * test(node-sdk): drop v1-only subagentNames from the resume parity projection Custom agent files made v1's resumed agent config carry the bound profile's delegatable subagent roster; v2's resumed agent state has no equivalent field, so the resume parity cases fail on main. Project the engine-owned field away instead of pinning it as a resume-data gap. * fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2 On the v2 engine route the /secondary_model command persisted the recipe but failed to apply it to the current session: the SDK method fell through to the base class's not_implemented getRpc(). v1 pushes a reloaded config snapshot into the session because its spawn binding, tool descriptions, and cached startup warning all read that snapshot. agent-core-v2 resolves the secondary model live against IConfigService at spawn time and rebuilds the tool description per read, so the setConfig write already takes effect session-wide. The override keeps the rest of v1's contract: config reload, the same loud validations (session lookup, persist-first recipe check, pointed-model resolution wrapped at [secondary_model].model), and a warning-cache refresh via a new recheckSecondaryModelWarning on the session warning service. getSessionWarnings also surfaces the v2 secondary-model warning next to the AGENTS.md one, matching v1's aggregate. * fix(agent-core-v2): surface the subagent's bound model on status events The v2 model slice rides only the bind-time agent.status.updated, which precedes subagent.spawned and is dropped by clients that key child events off the spawn, so subagent cards never learned the model — and a single-step run emits no usage/context slice until it ends, so the model only appeared at completion. Re-affirm the binding right after the spawn announcement via a new IAgentProfileService.republishStatus, and fold a consistent usage/context/model snapshot into every status event at both v1 edges (kap-server's broadcaster and the in-process SDK session wiring, resolving the secondary-model derived id to a readable display name. EOF ) * Delete .changeset/subagent-card-model.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * style(agent-core-v2): remove inline implementation comments --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> (cherry picked from commit b850c5f) * docs: fix dead anchor links in en/zh docs (MoonshotAI#2348) * docs: fix dead anchor links in en/zh docs - #loop_control -> #loop-control (heading slug uses hyphens) - #secondary_model -> #secondary-model - env-vars model section anchors: kimi_model -> kimi-model - provider credential section anchors: configtoml -> config-toml - /provider management anchors: point at the renamed heading in each locale - hooks: point the stale config-files#hooks reference at the local Configuration section - en files: replace two leftover Chinese anchors with their English targets * docs: add missing .md extension to themes page links --------- Co-authored-by: qer <wbxl2000@outlook.com> (cherry picked from commit f8ec3d1) * feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field (MoonshotAI#2314) * feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field * feat(agent-core-v2): add systemPromptPath to load plugin system prompt from a file * docs: explain plugin system prompt templates * fix(agent-core-v2): refresh plugin system prompts after changes * fix(agent-core-v2): freeze restored profile bindings and converge plugin contributions at session scope - restore no longer re-renders or re-persists prompts: a resumed agent keeps its replayed profile binding (prompt and tool set) as persisted - a new Session-level convergence point reloads plugin skills into the session skill catalog before fanning out to every live agent prompt, and every catalog-kind plugin mutation awaits the whole pipeline; MCP-only toggles carry a distinct change kind and skip it - live refreshes after a restart re-resolve the bound profile by name and rebind the full slice (prompt, disallowed tools, active tools) atomically, warning and keeping the persisted state when the profile is gone; renders reuse the first-render timestamp and unchanged prompts are not re-persisted, so convergence never churns the wire - cap plugin system-prompt contributions (32 KB per field/file, 64 KB aggregate per prompt build) with manifest diagnostics and warnings - bump the changeset to minor: this is a new user-facing capability * fix(agent-core-v2): register the new session domain and dedupe the missing-profile warning - add sessionPluginContribution to the domain-layer registry so lint:domain stays green - emit system-prompt-refresh-profile-missing once per profile name, matching the service's other deduped warnings - document the convergence timeout escape hatch and the klient exclusion of enabledSystemPrompts * fix(agent-core-v2): dedupe the plugin budget warning and surface section read failures - emit plugin-sections-oversized once per skipped-plugin signature - let enabledSystemPrompts failures propagate to the refresh catch (keeps the current prompt and warns) instead of silently rendering and persisting a prompt without plugin instructions - cover the convergence timeout cut-off with a fake-timers test - clarify that the first-render timestamp anchors per process * fix(agent-core-v2): serialize session convergence and restore onDidReload timing - run at most one convergence per session and bound each change's wait by the timeout, so a fan-out emitter never interleaves deliveries after a timed-out convergence - fire onDidReload as soon as the reload commits again, keeping hook reloads independent of prompt convergence - sign the plugin budget warning with an unambiguous key * docs(agent-core-v2): align convergence wording with the serialized semantics - the timeout retry promise only holds once stalled work clears - note the per-session serial delivery cost model on the plugin change contract and the dual-queue invariant on the service * fix(agent-core-v2): keep empty plugin sections byte-neutral in the prompt template - place ${plugin_sections} on the same template line as ${skills_section} so prompts without either block render exactly as before this feature - note on the change contract that waitUntil work must not call back into plugin mutations, and spell out the per-session convergence order in the user docs * fix(agent-core-v2): pin a fork's profile so refresh triggers never rebind it - applyBindingSnapshot left the fork with no pinned profile, which routed in-process forks into the post-restart catalog rebind and could reset an inherited tool set; forks now inherit the source agent's pinned profile object - pin the first-render timestamp reuse with a ${now}-embedding test and document the anchored ${now} semantics - tighten the plugin docs budget and resume-refresh wording * fix(agent-core-v2): join in-flight convergence during agent bootstrap - an agent created while a plugin convergence is in flight now waits for it, and a restored agent refreshes once after it, so a plugin mutation never straddles an agent's bootstrap - warn on a non-string systemPrompt field and strip a UTF-8 BOM from systemPromptPath files before trimming - correct the consumption-surface wording (every CLI surface on the experimental flag, not just kimi -p), the per-session queueing note, and the single-plugin combined budget clause * fix(agent-core-v2): bound the bootstrap convergence join by the timeout A permanently wedged convergence kept convergeTail pending forever, and the unconditional settled() wait in bindBootstrap would have blocked every later agent creation in that session; the join now races the shared convergence timeout and continues (a restored agent still refreshes once, which never touches the tail), and the timeout constant moves to the contract for reuse * fix(agent-core-v2): close the convergence race against in-progress restores - a convergence fan-out could land while an agent's wire log is still replaying, dispatching a replay-visible config record whose effect the rest of the replay then overwrites; refreshSystemPrompt now skips while the wire restore is in progress - convergence completion is tracked by a generation counter; bootstrap compares it (after a bounded join) and refreshes a restored agent exactly once when a round completed after its creation began, replacing the wasConverging flag that could miss both windows * fix(agent-core-v2): bound each convergence so a wedged participant cannot stop the pipeline - the fan-out now races the convergence timeout, so convergeTail always settles: a permanently hung refresh delays its round (blocked entries drain oldest-first on later changes) instead of killing the session's convergence for good - warn when agent bootstrap stops waiting on a stalled convergence - diagnose a blank systemPromptPath and pin the plugin-root escape guard with traversal, absolute-path, and symlink tests * fix(agent-core-v2): bound the skill reload, preserve user-tool overlays, roll the prompt clock daily - the convergence's skill-reload segment now races the same timeout as the fan-out, so no segment of the pipeline can wedge a session for good; it continues with the previous catalog and retries next change - a cold rebind that resets the tool set replays session-added user tools onto the new base instead of dropping them for the rest of the process - the rendered timestamp re-anchors when the UTC date rolls over, so long-lived processes keep a fresh clock while steady-state renders stay byte-stable within a day - the plugin budget warning dedupes per plugin id, and the docs note that systemPromptPath content is frozen until the next reload * feat(agent-core-v2): converge cold plugin changes on resume through a drift-free gate - restore replays the persisted binding untouched, then bootstrap refreshes only when drift-free inputs changed while the session was cold: the catalog profile's tool set/denylist, or the plugin-sections baseline persisted alongside the prompt on the existing bind/update payloads; directory-listing and date drift wait for live triggers, so quiet resumes append no replay-visible records - the rendered timestamp is day-precision (UTC date at 00:00, re-anchored on rollover), keeping steady-state renders byte-stable across resumes and sessions on the same day - consolidate both timeout helpers onto a shared raceOutcome, and drop the generation counter the gate supersedes - align the plugin-sections precedence prose with the AGENTS.md disclaimer (no self-granted authority, system instructions win on conflict) * fix(agent-core-v2): bound the restored-prompt gate and land the sections baseline - the gate's plugin-sections read now races the convergence timeout, so agent creation never blocks behind an unrelated plugin mutation - refreshes serialize per agent through a tail, so overlapping triggers cannot write prompts out of order - when plugin sections change but a plugin-free custom prompt does not, the new baseline lands as a sections-only update instead of making every later resume re-render in vain - align the system prompt's Date and Time paragraph with the day-precision anchored timestamp * Update plugin system-prompt instructions in changeset Live sessions pick up plugin changes, while the default TUI and `kimi -p` paths ignore these fields. Signed-off-by: 7Sageer <sag77r@hotmail.com> * refactor(agent-core-v2): keep plugin skill reload user-driven Plugin mutations still converge live agent prompts, but the session skill catalog goes back to refreshing only on explicit plugin reload, as before: the prompt feature does not need skill convergence, and the pre-existing manual-reload semantics stay uniform across all plugin contributions. Removes the convergence-driven skill reload, the reloadSource de-privatization, and their tests; restores the PluginSkillSource onDidReload forwarding and its catalog tests. * refactor(agent-core-v2): apply plugin system-prompt changes only on explicit reload Drop the live convergence machinery (the plugin onDidChange barrier, the sessionPluginContribution fan-out, the restored-prompt drift gate, and the day-precision render clock) so plugin system-prompt sections take effect at the same point as every other plugin contribution: /plugins reload or a new session. The profile now refreshes when the session skill catalog re-pulls its plugin source on reload, reading both the skill list and the prompt sections fresh. * feat(agent-core): let plugins contribute system prompt instructions via the manifest systemPrompt field * chore(agent-core-v2): remove inline implementation comment * docs: clarify plugin prompt refresh semantics --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> (cherry picked from commit 02d77b2) * feat: support plugin-contributed custom agents (MoonshotAI#2365) * feat: support plugin-contributed custom agents * fix: await plugin loading before agent catalog * fix: refresh plugin agents on v1 reload * test(agent-core-v2): add enabledSystemPrompts to the plugin service stub (cherry picked from commit fa2c5ce) * fix: remove the blocking wait from the TaskOutput tool (MoonshotAI#2379) * fix: remove the blocking wait from the TaskOutput tool The block/timeout parameters let a model stall the whole turn waiting for a background task (up to 3600s), even though completion already arrives via automatic notification. Remove both parameters from the v1 and v2 engines (kept in model-facing parity), simplify retrieval_status to success/not_ready, and update the tool, Bash, and Agent prompt wording plus user docs accordingly. Stale callers passing block are silently treated as a non-blocking snapshot. * fix: align background-task prompts with the non-blocking TaskOutput The compaction reminder promised TaskOutput could fetch a task's result for tasks that are still running, where it now returns not_ready — reword it to snapshot semantics and point at the completion notification. Also list AskUserQuestion(background=true) as a task source in the TaskOutput description. * test: exercise stale TaskOutput args through the runtime validator A stale block/timeout argument never reaches the tool: the executor's preflight validates args against the closed tool schema and rejects them immediately, so the old test documented silent-tolerance semantics the runtime never exhibits. Assert the real behavior through compileToolArgsValidator/validateToolArgs instead, and drop statement-adjacent comments to match the package's header-only comment convention. (cherry picked from commit 691ec46) * fix(vscode): all AskUserQuestions should be answered and added to the context (cherry picked from commit 3bbc3c5) * fix(vscode): open plan files from review (cherry picked from commit 729e69f) * fix(vscode): bind plan reviews to their files (cherry picked from commit f6b7f45) * fix(agent-core-v2): honor configured permission rules (cherry picked from commit ee0cc67) * fix(anthropic): omit incompatible tool schemas (cherry picked from commit 4e9ffbb) * fix(agent-core-v2): rescan agent profile catalog when a dispatch lookup misses Long-lived sessions (e.g. hosted by kimi web) scan agent file directories once at session materialization, so agent Markdown files written afterward can never be dispatched: the Agent tool, AgentSwarm tool, and swarm spawn path all fail with "Unknown agent type" until the server restarts. Add getProfileOrReload(), a small lookup helper in the sessionAgentProfileCatalog domain: on a miss it calls the catalog's existing (previously unwired) reload() once — deduped across concurrent misses — and retries before the caller's original error path applies. The three dispatch sites read the catalog live, so a rescan is visible to existing sessions immediately; the happy path is unchanged. (cherry picked from commit 27c4342) * fix(tui): refresh task output previews (cherry picked from commit 5d56451) * fix(tui): stop polling completed task output (cherry picked from commit 1a6df5b) * fix(tui): preserve final task output refresh (cherry picked from commit 56e7a72) * fix(tui): stop polling newly completed task output (cherry picked from commit ebe87b6) * fix(kimi-web): follow window width for the chat column The reading column was capped at 760px, leaving wide empty margins on large screens. Let it follow the pane width instead. (cherry picked from commit b6d88b5) * fix(kimi-web): swallow Ctrl+S in the composer to block the Save Page dialog preventDefault ran only while a turn was running, so an idle Ctrl+S/Cmd+S fell through to the browser and opened the Save Page dialog. Always swallow the shortcut; steering still only fires when a turn is running. (cherry picked from commit 7e44ece) * fix(kimi-web): anchor the conversation outline to the pane's right edge With the reading column no longer capped at a fixed width, the outline rail's old anchor (the 760px column edge) left it floating in the middle of the widened content. Pin the rail to the pane's right edge inside the scrollbar gutter, reveal labels leftward over the content on hover, and measure the expanded fit in that direction. (cherry picked from commit 6edc9c0) * fix(kimi-web): swallow Ctrl+S app-wide so the browser never opens Save Page The composer's keydown only fires while the composer is focused, so a Ctrl+S/Cmd+S pressed anywhere else still fell through to the browser's Save Page dialog. A capture-phase preventDefault in App.vue's global keydown now swallows the shortcut everywhere; steering stays in the composer's handler. (cherry picked from commit 0d9835e) * fix(kimi-web): keep code font metrics in bare markstream code blocks Plain-text code blocks rendered without the container wrapper nest <code> deeper than pre > code, so the inline-code chip rule (font: .9em) applied inside them and shrank the code to 10.8px/normal while the line-number overlay stayed at 12px/18px — the numbers drifted progressively below their lines. Extend the font:inherit guard to all markstream pres. (cherry picked from commit c1eab79) * fix(kimi-web): drop the TOC hover bridge and uncap the design-system demo The rail's invisible hover bridge sat over the right edge of full-width content and intercepted clicks and text selection from the messages beneath it; only the actual outline rows receive pointer events now. Also drop the 560px cap from the design-system chat demo so the spec matches the full-width behavior. (cherry picked from commit d78caaf) * Infer the Anthropic wire from a provider's /anthropic endpoint path The models.dev catalog resolver decided a provider's wire from type/npm/id only. A provider exposing its Anthropic-compatible surface under a /anthropic path, without an explicit type or an anthropic/claude token in npm/id, fell through to the OpenAI-compatible fallback and persisted the Anthropic endpoint under the OpenAI wire. Infer the Anthropic wire from a /anthropic endpoint path segment (matched on the path only), in both lockstep copies of the resolver, with unit and import-integration coverage. (cherry picked from commit 83e27aa) * fix(agent-core): nudge todo reconciliation when a turn ends with unfinished todos (cherry picked from commit aae8d7f) * fix(agent-core): honor same-turn TodoList writes in the turn-end reminder Addresses review feedback: the turn-end check now scans the whole just-finished turn (back to the user prompt that started it) for a TodoList write, so a mid-turn update followed by a closing text reply no longer triggers a spurious reminder. Also adds the required changeset. (cherry picked from commit 81f48ee) * fix(kap-server): defer global search activation (cherry picked from commit b514266) * fix(tui): preserve quotes in Windows status line commands (cherry picked from commit a555f1f) * test(tui): stabilize Windows quote regression (cherry picked from commit b7f549e) * fix(agent-core-v2): preserve select_tools for disclosure (cherry picked from commit a361352) * test(agent-core-v2): align optional harness option (cherry picked from commit bb3d4c8) * fix(tui): bound completed shell output frames (cherry picked from commit 6fb6bac) * feat(hooks): expose effective permission mode (cherry picked from commit 2dd7d01) * fix(mcp): reinitialize expired HTTP sessions (cherry picked from commit e03fd73) * fix(acp): stream agent-initiated turns (cherry picked from commit 0d7614c) * chore: prepare kkm 0.30.0 portable upstream release --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> Co-authored-by: 7Sageer <sag77r@hotmail.com> Co-authored-by: wenhua020201-arch <wenhua020201@gmail.com> Co-authored-by: qer <wbxl2000@outlook.com> Co-authored-by: Kai <me@kaiyi.cool> Co-authored-by: rickgao <rickgao@tencent.com> Co-authored-by: zhaojy <zhaojy01@rd.netase.com> Co-authored-by: ericzhao.zhao <ericzhao.zhao@cyberklick.com> Co-authored-by: gunnlace <docdoby14470814@163.com> Co-authored-by: Ben Younes <benyounes.ousama@gmail.com> Co-authored-by: lostforwurdz <222525123+lostforwurdz@users.noreply.github.com> Co-authored-by: Tolik Trek <tolik.trek@gmail.com> Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com> Co-authored-by: airudotsh <airudotsh@users.noreply.github.com> Co-authored-by: Xule Lin <43122877+linxule@users.noreply.github.com> Co-authored-by: luren <lurenjia534@outlook.com> Co-authored-by: Codex <codex@openai.com>
7 tasks
11 tasks
RealKai42
added a commit
that referenced
this pull request
Aug 4, 2026
…ange Reloading the catalog on a `builtin_product_skills` toggle left existing agents holding the old listing: `AgentProfileService` refreshes the prompt only for the plugin source, so a disabled switch kept advertising skills that were gone, and enabling it left them missing until an unrelated refresh. The plugin source is special because it also contributes prompt sections (#2314), and the file-backed sources are left out for cost — their fs watches would rebuild every agent's prompt on each edit. The builtin source has no watch: it changes only when its config switch is toggled, so it belongs with the plugin source rather than with the file ones. Subscribing to the catalog rather than the config section is load-bearing. The catalog fires after the contribution is replaced, whereas a config subscription would race the reload, and `resolveSkillListing` only awaits the catalog's *initial* readiness — so the rebuilt prompt could read the listing it was meant to replace. The source id is a named constant now, so the subscription does not match on a bare string.
RealKai42
added a commit
that referenced
this pull request
Aug 4, 2026
* refactor(agent-core-v2): simplify context tags and shared copy
Rename the context-injection tags to `<skill-loaded>` and
`<plugin-instructions>`, drop the product prefix from the CronCreate tool
description and the default agent description, and point the MCP OAuth
callback page back to "your terminal" instead of naming one client.
The callback page is shared by the ACP host, the web UI, and embedding
hosts, so naming a single client was inaccurate there. The tags and the
two descriptions read exactly the same without the prefix. Verified no
runtime consumer matches the old tag names; the updated snapshots cover
the tool descriptions that changed.
* feat(agent-core-v2): add a switch for the product-documentation skills
Five builtin skills document this CLI itself — `update-config`,
`custom-theme`, `mcp-config`, `check-kimi-code-docs`, and
`import-from-cc-codex`. Their names and descriptions sit in the system
prompt on every turn, which is dead weight for runs that will never
reconfigure the CLI.
Add a top-level `builtin_product_skills` field (also settable through
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS`) to drop them. On by default, so
nothing changes unless it is set; the trade when off is that the model
loses the guided flows for those tasks.
Filtering happens where the catalog is assembled — a later filter would
leave the skills advertised to the model. The whole section is one
scalar, so it exercises the section-level env binding branch and needs
its own strip: `stripEnvBoundFields` only walks object fields, so an env
override would otherwise be written back into `config.toml`.
* feat(agent-core-v2): add custom agent identity
Add an `[identity]` config section (`name`, optional `slug`, both also
settable through `KIMI_CODE_IDENTITY_NAME` / `KIMI_CODE_IDENTITY_SLUG`)
that sets the identity the agent presents: the name it calls itself in
the system prompt, the `User-Agent` product token sent to third-party
providers, and the client name announced to MCP servers. Leaving it
unset changes nothing.
Until now every one of these was fixed, which left no way to run the
agent as part of another product — an internal deployment, a fork with
its own branding, an embedding host.
The identity resolves inside the engine rather than being seeded by each
host, so it applies to every launch surface — including headless runs,
which today seed no display name at all and fall through to the built-in
default.
Two deliberate asymmetries:
- The display name is a filling value with a fallback chain (config >
host-declared > the consumer's own default); the slug is a rewriting
value with two states only, so with no identity configured the
rewriting paths are equivalent to not existing.
- The rewrite happens in the outbound header assembly, the one layer
that knows which vendor it is building for. Vendors declaring
`hostHeaders: 'full'` keep the host's own product token, which that
header set is built around and which backends key on; the configured
identity applies to the third-party path.
Resolution is lazy throughout: config loads asynchronously, and a
constructor snapshot would freeze the pre-load value under some startup
orderings.
Two input edges the resolver has to absorb, since both would otherwise
reach the User-Agent builder and either break it or quietly rewrite the
header: blank and whitespace-only values read as unset in the file just
as they already did in the env, so a stray `name = ""` cannot claim an
identity; and a name that folds away to nothing under slug
normalization (a CJK-only name, say) falls back to a neutral token
rather than producing a blank product, which the builder rejects.
* fix(agent-core-v2): keep the file value when a scalar env binding fails to parse
`config.ts` documents that an env value failing its binding's `parse` is
ignored, and `applyEnvBindings` honors that for object fields by
assigning only when the resolved value is defined. `applySectionEnv`
returned the parse result straight through for whole-section scalar
bindings, so a blank or mistyped variable resolved to `undefined` and
cleared the configured file value instead of being ignored.
Nothing hit this before: every existing section either binds object
fields or is env-only. `builtin_product_skills` is the first
whole-section scalar binding, where exporting an empty or misspelled
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS` would silently undo a configured
`false`.
* feat(agent-core-v2): extend the custom identity to discovery and global MCP
Two outbound paths still announced the built-in product name under a
configured identity:
- `DiscoveryService` read the host User-Agent straight from bootstrap
args when refreshing provider models, so custom registries — which are
third-party endpoints — saw the original token while chat requests to
the same class of endpoint saw the configured one.
- `SDKRpcClientV2` builds its own global `McpOAuthService` plus a
throwaway `McpConnectionManager` for server testing, neither of which
goes through the workspace-owned manager that carries the resolver.
Both now resolve the identity from the App scope.
* refactor(agent-core-v2): neutralize remaining copy and align comments
The synthetic MCP authentication tool description is injected into the
model context and still named the product; it and the OAuth callback
pages now use client-neutral wording. "Return to your terminal" was no
improvement over naming a client — both assume what the host is, and
that page serves the ACP host, the web UI and embedding hosts alike.
Comments introduced by the identity work move into their module headers,
per the domain convention. Interface field docs stay: the rule names
functions, methods and statements, and field-level docs are established
across the codebase.
The new tests gain scenario headers and dispose the scoped hosts they
create, and the `[identity]` docs state which engine reads the section.
* fix(agent-core-v2): read the product-skill switch after config is ready
`BuiltinSkillSource` is the lowest-priority skill source, so the workspace
catalog loads it first — before `IConfigService` has finished loading — and
keeps the contribution it returns for the life of the handler, with no
reload path and no change event. Reading `builtin_product_skills` eagerly
therefore stranded the startup configuration: an explicit `false` could be
ignored for the whole process. `UserFileSkillSource` already awaits config
readiness for exactly this ordering; this source now does the same.
Also record the identity collaborator in the two module headers that gained
the dependency without documenting it, and scope the
`builtin_product_skills` docs to the engine that reads it, matching the
note the identity section already carries.
* fix(agent-core-v2): apply the product-skill switch to session-less listings
`builtin_product_skills = false` only reached the scoped skill source. The
SDK's `listWorkspaceSkills` and the server's `GET /workspaces/{id}/skills`
both composed the raw `BUILTIN_SKILLS` constant, and the web app feeds its
pre-session onboarding menu from that route — so the five product skills
stayed listed until a session existed, then vanished from the session's
catalog.
Move the decision into `visibleBuiltinSkills(enabled)` next to the constant
and route every consumer through it, reading the switch via the shared
`builtinProductSkillsEnabled`. Keeping "what counts as a product skill" in
one place is the point: three copies of the predicate would drift the next
time a builtin is added. The SDK listing also awaits config readiness,
which it did not do before.
* fix(node-sdk): await config before materializing the global MCP OAuth provider
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, and the preceding `globalMcpConfig.get()` reads
`mcp.json` directly rather than through `IConfigService`. So a
`beginGlobalMcpServerAuth` call made right after the harness is created
could resolve the identity before config finished loading, pinning the
built-in label for the rest of the process — including the OAuth dynamic
registration a third-party MCP server records.
`testGlobalMcpServer` already awaited config readiness for its own reasons;
this path now does too.
* refactor(agent-core-v2): drop the unused builtin-skill registrar
`registerBuiltinSkills` stamped the raw constant into a catalog for "edge
composition without a Session" — exactly the shape that now has to respect
`builtin_product_skills`. It has no callers in v2 and is not exported from
the package index, so it was dead code that also stood as an invitation to
bypass the switch. v1 keeps its own copy.
Every remaining path composes builtins through `visibleBuiltinSkills`.
* fix(agent-core-v2): send the configured identity on custom-registry imports
`:import_registry` fetched a user-supplied third-party URL with a
hardcoded `kimi-code-kap-server` User-Agent, so the first request to a
registry announced the product while every scheduled refresh of the same
registry announced the configured identity. The hardcoded value was wrong
on its own terms too: that token names the server, and this path also runs
in the CLI.
Both services now project the identity through `identityUserAgent`, which
carries the two guards (no host header, or no identity) once instead of
per caller. The model catalog keeps an inline copy on purpose — kosong is
a foundational layer and must not import an app domain.
Sweeping the remaining outbound User-Agent sources found no further gaps:
WebFetch deliberately sends a Chrome-like UA, the models.dev catalog fetch
sends none from the CLI, and kap-server's `user-agent` reads are inbound.
* docs: scope the identity env vars and condense the changeset
The environment-variable reference advertised all three new variables
without noting that only the agent-core-v2 engine reads them; the
configuration page already carried that note. Added in both locales.
The changeset had grown into two paragraphs of implementation detail,
which is what would land in the CLI release changelog. `gen-changesets`
asks for one short sentence plus at most a one-line usage hint.
* docs(agent-core-v2): describe the identity as what the agent calls itself
The module headers had drifted into describing the feature by what it
keeps off the wire rather than what it configures. Reworded so they state
the capability: the identity is the name the agent uses for itself, and
the unset case is a no-op rather than something "safe". The product-skill
switch excludes skills rather than hiding them.
Wording only; behavior and structure unchanged.
* test(agent-core-v2): cover the identity on custom-registry imports
The import path switched from a hardcoded `kimi-code-kap-server` token to
the host User-Agent projected through the identity, but nothing asserted
it. Two cases pin both halves: a configured identity reaches the request,
and an unconfigured one leaves the host header intact — the second matters
because a single case would also pass if one hardcoded value had simply
replaced another.
Both fail against the previous implementation.
* fix(node-sdk): guard every global MCP OAuth path behind config readiness
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, so any path that can materialize a provider has
to run after config has loaded. `beginGlobalMcpServerAuth` awaited
readiness, but `resetGlobalMcpServerAuth` reaches the same cache through
`invalidate()` -> `getProvider()` without waiting: resetting auth right
after the harness is constructed pinned the built-in client name, and the
await added to the begin path could not help because it then reused that
cached provider.
Rather than add the missing await, the accessor is now async and holds the
guard itself, so the service cannot be obtained before config is ready and
a future entry point cannot forget. The remaining `configReady` in
`testGlobalMcpServer` stays — that one is for its own `[mcp]` section read.
* fix(agent-core-v2): send the configured identity on models.dev requests
The directory fetch behind `listModelsDevProviders` / `getModelsDevProvider`
still hardcoded a `kimi-code-kap-server` User-Agent, so browsing or importing
from models.dev announced the built-in product — and claimed to be the server
even when running in the CLI. Only the custom-registry import had been fixed.
`getModelsDevCatalog` now takes the User-Agent from its caller: the module is
plain module-level state with no container access, and the value depends on
the host and the configured identity, which only the calling service can see.
All four third-party fetches in that service share one helper.
Where the host states no User-Agent, a neutral token stands in rather than
dropping the header — these are directories the service chooses to call, so
there is no host intent to preserve, unlike the provider requests the model
catalog assembles.
Both new tests fail against the previous hardcoded value.
* test(agent-core-v2): assert the product-skill set literally
The expected sets were derived from the same `productSpecific` field the
production filter reads, so a builtin silently losing its marker would just
move between sets and leave every assertion green — while staying visible to
the model once the switch is off. The five names are now literal, with a test
asserting the marked set matches them exactly.
Dropping the marker from one skill now fails four tests instead of none.
Also states the App scope in the identity contract header, per the domain's
comment convention for contract files.
* fix(agent-core-v2): normalize the host-declared display name too
Blank and padded values were normalized on the config side but not on the
host fallback, so an embedding host passing `displayName: " "` rendered
"You are ," into the system prompt, and a padded name kept its padding.
Same rule now applies to every source of the name.
Also names `agentIdentity` as the collaborator in the request-headers
adapter header, which described the value it obtains without saying which
domain resolves it.
The three new cases fail against the previous implementation.
* fix(agent-core-v2): keep the configured slug when the host sends no User-Agent
The neutral fallback added for hosts that state no `User-Agent` discarded a
configured identity along with it: `identityUserAgent` returns `undefined`
as soon as there is no host header to rewrite, so `?? DEFAULT_IDENTITY_SLUG`
sent the literal `agent` even when `[identity].slug` was set — precisely the
case that fallback exists to serve. The configured slug now stands on its
own, with the neutral token reserved for having neither.
The four combinations of (host header, configured slug) had three tests; the
missing one is the one that was wrong. It now fails without this change.
`outboundUserAgent` also awaits config readiness before reading the identity,
so a browse issued right after bootstrap cannot send the pre-load value — the
guard lives in the accessor rather than at its four call sites, matching how
the same race is handled elsewhere in this branch.
Both headers here and in `discoveryService` now name `agentIdentity` as the
collaborator resolving that token.
* test(acp-server): follow the renamed skill-activation tag
`acp-server` arrived on main after the tag rename, so its two assertions
still expected `kimi-skill-loaded` and failed once the branches met. Also
updates the web app's CSS comment, which named the old tag from the start
of this branch — a comment, so nothing ever failed on it.
Found by CI: the merge verification only ran agent-core-v2's suite, and
this package is neither a dependency nor a dependent of it.
* fix(agent-core-v2): present the configured slug on registry refreshes too
The previous round taught the import path to fall back to the configured
slug when the host states no `User-Agent`, but left the scheduled refresh
of the same registry on the bare projection — so one registry could see
`acme` on import and the runtime default on refresh.
Extracting `identityUserAgent` had made the two paths share a function
without sharing the policy. The choice itself is now the shared piece:
`identityUserAgentOrDefault` always yields a value, for the directories
this process chooses to call, while `identityUserAgent` stays the form
that rewrites only what the host already sends — what a provider request
needs, where the host's silence is its own choice.
* docs(agent-core-v2): move new member docs into the module headers
The domain's comment convention is absolute — comments live solely in the
top-of-file block — and I had read the "functions, methods, or statements"
clause as leaving interface members out. It does not: only 25 of 734 v2
sources carry an indented block, so the members I documented were the
exception, not the pattern.
Seven members across six files move into their headers. `types.ts` had no
header at all, so it gains one.
* fix(agent-core-v2): connect session MCP overlays after config is ready
The shared manager reaches `connectAll` through `initialize()`, which awaits
the config domain first; `sessionOverlay` called it straight away. A session
carrying ephemeral `mcpServers` created right after bootstrap therefore
resolved the client name before config had loaded and initialized under the
built-in one.
The blast radius is wider than that one connection: a remote server sends
the overlay through `hasTokens()`, which materializes an OAuth provider on
the *shared* service and caches it by store key — so the early name outlives
the connection that raced. The overlay now connects behind `mcpConfig.ready`,
leaving the returned readiness promise unchanged.
* fix(agent-core-v2): reload builtin skills when their switch changes
The workspace catalog keeps each source's contribution for the life of the
handler, so a `builtin_product_skills` toggle never reached an existing
handler's sessions. That was harmless while every surface read the same
constant — but routing the session-less listings through the config made the
two views disagree, since those read the switch on every call.
Follows `ExtraFileSkillSource`: subscribe to the owning section and fire
`onDidChange`, which the catalog already turns into a source reload. The
test asserts an unrelated section does not trigger it.
* fix(agent-core-v2): apply the identity to self-configured web services
`[services.moonshot_search]` and `[services.moonshot_fetch]` name their own
`base_url`, so both services can point at an endpoint the user chose — but
each forwarded the host request headers verbatim, sending the built-in
product token there under a configured identity.
Only the services-config path is rewritten; the managed OAuth path keeps the
host headers as they are, being the endpoint the session authenticated
against. The distinction is the same one the model catalog draws per vendor.
`identityHeaders` carries the rewrite across a whole header set, so this is
the fourth caller sharing the projection rather than repeating its guards.
A pair of tests pins both halves.
My earlier sweep classified these two as official by their names instead of
asking who chooses the URL, which is why they were missed. The contract
header is also condensed here, per the convention below.
* docs(agent-core-v2): condense the identity headers to their contracts
The comment convention is one sentence with two halves — comments live only
in the top-of-file block, *and* that block states the module's role without
narrating implementation. Moving the member docs up last round satisfied the
first and broke the second: the headers ended up spelling out the slug
folding algorithm, the strip mechanics, and the load order.
Kept what a caller or the next editor would get wrong without it (why the
value is read rather than snapshotted, what `undefined` obliges a consumer
to do, why this source waits for config). Dropped what the code already
says. 22/12/12/13 lines, against 53 in `catalogService.ts` — length was
never the problem.
* fix(agent-core-v2): rebuild active prompts when the builtin skills change
Reloading the catalog on a `builtin_product_skills` toggle left existing
agents holding the old listing: `AgentProfileService` refreshes the prompt
only for the plugin source, so a disabled switch kept advertising skills
that were gone, and enabling it left them missing until an unrelated
refresh.
The plugin source is special because it also contributes prompt sections
(#2314), and the file-backed sources are left out for cost — their fs
watches would rebuild every agent's prompt on each edit. The builtin source
has no watch: it changes only when its config switch is toggled, so it
belongs with the plugin source rather than with the file ones.
Subscribing to the catalog rather than the config section is load-bearing.
The catalog fires after the contribution is replaced, whereas a config
subscription would race the reload, and `resolveSkillListing` only awaits
the catalog's *initial* readiness — so the rebuilt prompt could read the
listing it was meant to replace.
The source id is a named constant now, so the subscription does not match
on a bare string.
* refactor(agent-core-v2): freeze the agent identity for the process lifetime
The identity is announced outward (MCP initialize, OAuth registration,
provider request logs) and cannot be re-announced, so mid-process changes
could only ever apply partially. Resolve it once when config first loads
and hold it for the life of the process: IAgentIdentity now hands out a
frozen snapshot via resolved()/current(), carrying finished products
(outbound User-Agent variants, rewritten header set) so call sites stop
composing host headers with the slug themselves. The kosong host-headers
port carries two finished layers and the catalog only picks one; consumers
gain no invalidation obligations because the value can never change after
the freeze. [identity] edits take effect on the next start (documented).
* fix(agent-core-v2): locate the User-Agent header case-insensitively
HTTP header names are case-insensitive, but the snapshot builder looked up
'User-Agent' by exact key: an embedding host spelling it 'user-agent' got no
third-party UA and kept its own product token on the services path even with
an identity configured. The builder now locates every case variant and
rewrites each in place, keeping the host's spelling. Also corrects the two
web-service headers that still described both paths as sending the bootstrap
headers, naming agentIdentity as the collaborator behind the config path,
and documents that a resumed session keeps its recorded system prompt.
* fix(agent-core-v2): attribute header provenance from the finished third-party layer
Inspection reconstructed the non-full host layer from the raw headers with an
exact-case 'User-Agent' lookup, so a host spelling the header 'user-agent'
got a resolved User-Agent with no provenance entry even though the runtime
sends the rewritten value. buildModel now captures the port's finished
third-party layer in the trace and attribution reads it, keeping inspect()
on the same resolution pass as get(). Also condenses the identity contract
header to its external role, and documents that an existing MCP OAuth
authorization keeps the client registration it was granted under (reset the
server's auth to register under the new identity).
* fix(agent-core-v2): keep web tool backends from racing the identity freeze
An env-configured [services] endpoint is visible before config finishes
loading, and FetchURLTool / WebSearchTool materialized their backends at
construction — so a fast bootstrap could hit the identity snapshot's
pre-freeze guard during agent creation, and the composed backend pinned
config and login state for the agent's lifetime against the service's
documented per-call resolution. Both tools now resolve their backend per
invocation, the WebSearch activation gate checks presence alone through the
new hasWebSearchProvider() (no provider composition, no identity read), and
bind() awaits the identity freeze before materializing the model, whose
resolution reads the identity through the host-headers port.
mbuckaway
pushed a commit
to mbuckaway/kimi-code
that referenced
this pull request
Aug 4, 2026
* refactor(agent-core-v2): simplify context tags and shared copy
Rename the context-injection tags to `<skill-loaded>` and
`<plugin-instructions>`, drop the product prefix from the CronCreate tool
description and the default agent description, and point the MCP OAuth
callback page back to "your terminal" instead of naming one client.
The callback page is shared by the ACP host, the web UI, and embedding
hosts, so naming a single client was inaccurate there. The tags and the
two descriptions read exactly the same without the prefix. Verified no
runtime consumer matches the old tag names; the updated snapshots cover
the tool descriptions that changed.
* feat(agent-core-v2): add a switch for the product-documentation skills
Five builtin skills document this CLI itself — `update-config`,
`custom-theme`, `mcp-config`, `check-kimi-code-docs`, and
`import-from-cc-codex`. Their names and descriptions sit in the system
prompt on every turn, which is dead weight for runs that will never
reconfigure the CLI.
Add a top-level `builtin_product_skills` field (also settable through
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS`) to drop them. On by default, so
nothing changes unless it is set; the trade when off is that the model
loses the guided flows for those tasks.
Filtering happens where the catalog is assembled — a later filter would
leave the skills advertised to the model. The whole section is one
scalar, so it exercises the section-level env binding branch and needs
its own strip: `stripEnvBoundFields` only walks object fields, so an env
override would otherwise be written back into `config.toml`.
* feat(agent-core-v2): add custom agent identity
Add an `[identity]` config section (`name`, optional `slug`, both also
settable through `KIMI_CODE_IDENTITY_NAME` / `KIMI_CODE_IDENTITY_SLUG`)
that sets the identity the agent presents: the name it calls itself in
the system prompt, the `User-Agent` product token sent to third-party
providers, and the client name announced to MCP servers. Leaving it
unset changes nothing.
Until now every one of these was fixed, which left no way to run the
agent as part of another product — an internal deployment, a fork with
its own branding, an embedding host.
The identity resolves inside the engine rather than being seeded by each
host, so it applies to every launch surface — including headless runs,
which today seed no display name at all and fall through to the built-in
default.
Two deliberate asymmetries:
- The display name is a filling value with a fallback chain (config >
host-declared > the consumer's own default); the slug is a rewriting
value with two states only, so with no identity configured the
rewriting paths are equivalent to not existing.
- The rewrite happens in the outbound header assembly, the one layer
that knows which vendor it is building for. Vendors declaring
`hostHeaders: 'full'` keep the host's own product token, which that
header set is built around and which backends key on; the configured
identity applies to the third-party path.
Resolution is lazy throughout: config loads asynchronously, and a
constructor snapshot would freeze the pre-load value under some startup
orderings.
Two input edges the resolver has to absorb, since both would otherwise
reach the User-Agent builder and either break it or quietly rewrite the
header: blank and whitespace-only values read as unset in the file just
as they already did in the env, so a stray `name = ""` cannot claim an
identity; and a name that folds away to nothing under slug
normalization (a CJK-only name, say) falls back to a neutral token
rather than producing a blank product, which the builder rejects.
* fix(agent-core-v2): keep the file value when a scalar env binding fails to parse
`config.ts` documents that an env value failing its binding's `parse` is
ignored, and `applyEnvBindings` honors that for object fields by
assigning only when the resolved value is defined. `applySectionEnv`
returned the parse result straight through for whole-section scalar
bindings, so a blank or mistyped variable resolved to `undefined` and
cleared the configured file value instead of being ignored.
Nothing hit this before: every existing section either binds object
fields or is env-only. `builtin_product_skills` is the first
whole-section scalar binding, where exporting an empty or misspelled
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS` would silently undo a configured
`false`.
* feat(agent-core-v2): extend the custom identity to discovery and global MCP
Two outbound paths still announced the built-in product name under a
configured identity:
- `DiscoveryService` read the host User-Agent straight from bootstrap
args when refreshing provider models, so custom registries — which are
third-party endpoints — saw the original token while chat requests to
the same class of endpoint saw the configured one.
- `SDKRpcClientV2` builds its own global `McpOAuthService` plus a
throwaway `McpConnectionManager` for server testing, neither of which
goes through the workspace-owned manager that carries the resolver.
Both now resolve the identity from the App scope.
* refactor(agent-core-v2): neutralize remaining copy and align comments
The synthetic MCP authentication tool description is injected into the
model context and still named the product; it and the OAuth callback
pages now use client-neutral wording. "Return to your terminal" was no
improvement over naming a client — both assume what the host is, and
that page serves the ACP host, the web UI and embedding hosts alike.
Comments introduced by the identity work move into their module headers,
per the domain convention. Interface field docs stay: the rule names
functions, methods and statements, and field-level docs are established
across the codebase.
The new tests gain scenario headers and dispose the scoped hosts they
create, and the `[identity]` docs state which engine reads the section.
* fix(agent-core-v2): read the product-skill switch after config is ready
`BuiltinSkillSource` is the lowest-priority skill source, so the workspace
catalog loads it first — before `IConfigService` has finished loading — and
keeps the contribution it returns for the life of the handler, with no
reload path and no change event. Reading `builtin_product_skills` eagerly
therefore stranded the startup configuration: an explicit `false` could be
ignored for the whole process. `UserFileSkillSource` already awaits config
readiness for exactly this ordering; this source now does the same.
Also record the identity collaborator in the two module headers that gained
the dependency without documenting it, and scope the
`builtin_product_skills` docs to the engine that reads it, matching the
note the identity section already carries.
* fix(agent-core-v2): apply the product-skill switch to session-less listings
`builtin_product_skills = false` only reached the scoped skill source. The
SDK's `listWorkspaceSkills` and the server's `GET /workspaces/{id}/skills`
both composed the raw `BUILTIN_SKILLS` constant, and the web app feeds its
pre-session onboarding menu from that route — so the five product skills
stayed listed until a session existed, then vanished from the session's
catalog.
Move the decision into `visibleBuiltinSkills(enabled)` next to the constant
and route every consumer through it, reading the switch via the shared
`builtinProductSkillsEnabled`. Keeping "what counts as a product skill" in
one place is the point: three copies of the predicate would drift the next
time a builtin is added. The SDK listing also awaits config readiness,
which it did not do before.
* fix(node-sdk): await config before materializing the global MCP OAuth provider
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, and the preceding `globalMcpConfig.get()` reads
`mcp.json` directly rather than through `IConfigService`. So a
`beginGlobalMcpServerAuth` call made right after the harness is created
could resolve the identity before config finished loading, pinning the
built-in label for the rest of the process — including the OAuth dynamic
registration a third-party MCP server records.
`testGlobalMcpServer` already awaited config readiness for its own reasons;
this path now does too.
* refactor(agent-core-v2): drop the unused builtin-skill registrar
`registerBuiltinSkills` stamped the raw constant into a catalog for "edge
composition without a Session" — exactly the shape that now has to respect
`builtin_product_skills`. It has no callers in v2 and is not exported from
the package index, so it was dead code that also stood as an invitation to
bypass the switch. v1 keeps its own copy.
Every remaining path composes builtins through `visibleBuiltinSkills`.
* fix(agent-core-v2): send the configured identity on custom-registry imports
`:import_registry` fetched a user-supplied third-party URL with a
hardcoded `kimi-code-kap-server` User-Agent, so the first request to a
registry announced the product while every scheduled refresh of the same
registry announced the configured identity. The hardcoded value was wrong
on its own terms too: that token names the server, and this path also runs
in the CLI.
Both services now project the identity through `identityUserAgent`, which
carries the two guards (no host header, or no identity) once instead of
per caller. The model catalog keeps an inline copy on purpose — kosong is
a foundational layer and must not import an app domain.
Sweeping the remaining outbound User-Agent sources found no further gaps:
WebFetch deliberately sends a Chrome-like UA, the models.dev catalog fetch
sends none from the CLI, and kap-server's `user-agent` reads are inbound.
* docs: scope the identity env vars and condense the changeset
The environment-variable reference advertised all three new variables
without noting that only the agent-core-v2 engine reads them; the
configuration page already carried that note. Added in both locales.
The changeset had grown into two paragraphs of implementation detail,
which is what would land in the CLI release changelog. `gen-changesets`
asks for one short sentence plus at most a one-line usage hint.
* docs(agent-core-v2): describe the identity as what the agent calls itself
The module headers had drifted into describing the feature by what it
keeps off the wire rather than what it configures. Reworded so they state
the capability: the identity is the name the agent uses for itself, and
the unset case is a no-op rather than something "safe". The product-skill
switch excludes skills rather than hiding them.
Wording only; behavior and structure unchanged.
* test(agent-core-v2): cover the identity on custom-registry imports
The import path switched from a hardcoded `kimi-code-kap-server` token to
the host User-Agent projected through the identity, but nothing asserted
it. Two cases pin both halves: a configured identity reaches the request,
and an unconfigured one leaves the host header intact — the second matters
because a single case would also pass if one hardcoded value had simply
replaced another.
Both fail against the previous implementation.
* fix(node-sdk): guard every global MCP OAuth path behind config readiness
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, so any path that can materialize a provider has
to run after config has loaded. `beginGlobalMcpServerAuth` awaited
readiness, but `resetGlobalMcpServerAuth` reaches the same cache through
`invalidate()` -> `getProvider()` without waiting: resetting auth right
after the harness is constructed pinned the built-in client name, and the
await added to the begin path could not help because it then reused that
cached provider.
Rather than add the missing await, the accessor is now async and holds the
guard itself, so the service cannot be obtained before config is ready and
a future entry point cannot forget. The remaining `configReady` in
`testGlobalMcpServer` stays — that one is for its own `[mcp]` section read.
* fix(agent-core-v2): send the configured identity on models.dev requests
The directory fetch behind `listModelsDevProviders` / `getModelsDevProvider`
still hardcoded a `kimi-code-kap-server` User-Agent, so browsing or importing
from models.dev announced the built-in product — and claimed to be the server
even when running in the CLI. Only the custom-registry import had been fixed.
`getModelsDevCatalog` now takes the User-Agent from its caller: the module is
plain module-level state with no container access, and the value depends on
the host and the configured identity, which only the calling service can see.
All four third-party fetches in that service share one helper.
Where the host states no User-Agent, a neutral token stands in rather than
dropping the header — these are directories the service chooses to call, so
there is no host intent to preserve, unlike the provider requests the model
catalog assembles.
Both new tests fail against the previous hardcoded value.
* test(agent-core-v2): assert the product-skill set literally
The expected sets were derived from the same `productSpecific` field the
production filter reads, so a builtin silently losing its marker would just
move between sets and leave every assertion green — while staying visible to
the model once the switch is off. The five names are now literal, with a test
asserting the marked set matches them exactly.
Dropping the marker from one skill now fails four tests instead of none.
Also states the App scope in the identity contract header, per the domain's
comment convention for contract files.
* fix(agent-core-v2): normalize the host-declared display name too
Blank and padded values were normalized on the config side but not on the
host fallback, so an embedding host passing `displayName: " "` rendered
"You are ," into the system prompt, and a padded name kept its padding.
Same rule now applies to every source of the name.
Also names `agentIdentity` as the collaborator in the request-headers
adapter header, which described the value it obtains without saying which
domain resolves it.
The three new cases fail against the previous implementation.
* fix(agent-core-v2): keep the configured slug when the host sends no User-Agent
The neutral fallback added for hosts that state no `User-Agent` discarded a
configured identity along with it: `identityUserAgent` returns `undefined`
as soon as there is no host header to rewrite, so `?? DEFAULT_IDENTITY_SLUG`
sent the literal `agent` even when `[identity].slug` was set — precisely the
case that fallback exists to serve. The configured slug now stands on its
own, with the neutral token reserved for having neither.
The four combinations of (host header, configured slug) had three tests; the
missing one is the one that was wrong. It now fails without this change.
`outboundUserAgent` also awaits config readiness before reading the identity,
so a browse issued right after bootstrap cannot send the pre-load value — the
guard lives in the accessor rather than at its four call sites, matching how
the same race is handled elsewhere in this branch.
Both headers here and in `discoveryService` now name `agentIdentity` as the
collaborator resolving that token.
* test(acp-server): follow the renamed skill-activation tag
`acp-server` arrived on main after the tag rename, so its two assertions
still expected `kimi-skill-loaded` and failed once the branches met. Also
updates the web app's CSS comment, which named the old tag from the start
of this branch — a comment, so nothing ever failed on it.
Found by CI: the merge verification only ran agent-core-v2's suite, and
this package is neither a dependency nor a dependent of it.
* fix(agent-core-v2): present the configured slug on registry refreshes too
The previous round taught the import path to fall back to the configured
slug when the host states no `User-Agent`, but left the scheduled refresh
of the same registry on the bare projection — so one registry could see
`acme` on import and the runtime default on refresh.
Extracting `identityUserAgent` had made the two paths share a function
without sharing the policy. The choice itself is now the shared piece:
`identityUserAgentOrDefault` always yields a value, for the directories
this process chooses to call, while `identityUserAgent` stays the form
that rewrites only what the host already sends — what a provider request
needs, where the host's silence is its own choice.
* docs(agent-core-v2): move new member docs into the module headers
The domain's comment convention is absolute — comments live solely in the
top-of-file block — and I had read the "functions, methods, or statements"
clause as leaving interface members out. It does not: only 25 of 734 v2
sources carry an indented block, so the members I documented were the
exception, not the pattern.
Seven members across six files move into their headers. `types.ts` had no
header at all, so it gains one.
* fix(agent-core-v2): connect session MCP overlays after config is ready
The shared manager reaches `connectAll` through `initialize()`, which awaits
the config domain first; `sessionOverlay` called it straight away. A session
carrying ephemeral `mcpServers` created right after bootstrap therefore
resolved the client name before config had loaded and initialized under the
built-in one.
The blast radius is wider than that one connection: a remote server sends
the overlay through `hasTokens()`, which materializes an OAuth provider on
the *shared* service and caches it by store key — so the early name outlives
the connection that raced. The overlay now connects behind `mcpConfig.ready`,
leaving the returned readiness promise unchanged.
* fix(agent-core-v2): reload builtin skills when their switch changes
The workspace catalog keeps each source's contribution for the life of the
handler, so a `builtin_product_skills` toggle never reached an existing
handler's sessions. That was harmless while every surface read the same
constant — but routing the session-less listings through the config made the
two views disagree, since those read the switch on every call.
Follows `ExtraFileSkillSource`: subscribe to the owning section and fire
`onDidChange`, which the catalog already turns into a source reload. The
test asserts an unrelated section does not trigger it.
* fix(agent-core-v2): apply the identity to self-configured web services
`[services.moonshot_search]` and `[services.moonshot_fetch]` name their own
`base_url`, so both services can point at an endpoint the user chose — but
each forwarded the host request headers verbatim, sending the built-in
product token there under a configured identity.
Only the services-config path is rewritten; the managed OAuth path keeps the
host headers as they are, being the endpoint the session authenticated
against. The distinction is the same one the model catalog draws per vendor.
`identityHeaders` carries the rewrite across a whole header set, so this is
the fourth caller sharing the projection rather than repeating its guards.
A pair of tests pins both halves.
My earlier sweep classified these two as official by their names instead of
asking who chooses the URL, which is why they were missed. The contract
header is also condensed here, per the convention below.
* docs(agent-core-v2): condense the identity headers to their contracts
The comment convention is one sentence with two halves — comments live only
in the top-of-file block, *and* that block states the module's role without
narrating implementation. Moving the member docs up last round satisfied the
first and broke the second: the headers ended up spelling out the slug
folding algorithm, the strip mechanics, and the load order.
Kept what a caller or the next editor would get wrong without it (why the
value is read rather than snapshotted, what `undefined` obliges a consumer
to do, why this source waits for config). Dropped what the code already
says. 22/12/12/13 lines, against 53 in `catalogService.ts` — length was
never the problem.
* fix(agent-core-v2): rebuild active prompts when the builtin skills change
Reloading the catalog on a `builtin_product_skills` toggle left existing
agents holding the old listing: `AgentProfileService` refreshes the prompt
only for the plugin source, so a disabled switch kept advertising skills
that were gone, and enabling it left them missing until an unrelated
refresh.
The plugin source is special because it also contributes prompt sections
(MoonshotAI#2314), and the file-backed sources are left out for cost — their fs
watches would rebuild every agent's prompt on each edit. The builtin source
has no watch: it changes only when its config switch is toggled, so it
belongs with the plugin source rather than with the file ones.
Subscribing to the catalog rather than the config section is load-bearing.
The catalog fires after the contribution is replaced, whereas a config
subscription would race the reload, and `resolveSkillListing` only awaits
the catalog's *initial* readiness — so the rebuilt prompt could read the
listing it was meant to replace.
The source id is a named constant now, so the subscription does not match
on a bare string.
* refactor(agent-core-v2): freeze the agent identity for the process lifetime
The identity is announced outward (MCP initialize, OAuth registration,
provider request logs) and cannot be re-announced, so mid-process changes
could only ever apply partially. Resolve it once when config first loads
and hold it for the life of the process: IAgentIdentity now hands out a
frozen snapshot via resolved()/current(), carrying finished products
(outbound User-Agent variants, rewritten header set) so call sites stop
composing host headers with the slug themselves. The kosong host-headers
port carries two finished layers and the catalog only picks one; consumers
gain no invalidation obligations because the value can never change after
the freeze. [identity] edits take effect on the next start (documented).
* fix(agent-core-v2): locate the User-Agent header case-insensitively
HTTP header names are case-insensitive, but the snapshot builder looked up
'User-Agent' by exact key: an embedding host spelling it 'user-agent' got no
third-party UA and kept its own product token on the services path even with
an identity configured. The builder now locates every case variant and
rewrites each in place, keeping the host's spelling. Also corrects the two
web-service headers that still described both paths as sending the bootstrap
headers, naming agentIdentity as the collaborator behind the config path,
and documents that a resumed session keeps its recorded system prompt.
* fix(agent-core-v2): attribute header provenance from the finished third-party layer
Inspection reconstructed the non-full host layer from the raw headers with an
exact-case 'User-Agent' lookup, so a host spelling the header 'user-agent'
got a resolved User-Agent with no provenance entry even though the runtime
sends the rewritten value. buildModel now captures the port's finished
third-party layer in the trace and attribution reads it, keeping inspect()
on the same resolution pass as get(). Also condenses the identity contract
header to its external role, and documents that an existing MCP OAuth
authorization keeps the client registration it was granted under (reset the
server's auth to register under the new identity).
* fix(agent-core-v2): keep web tool backends from racing the identity freeze
An env-configured [services] endpoint is visible before config finishes
loading, and FetchURLTool / WebSearchTool materialized their backends at
construction — so a fast bootstrap could hit the identity snapshot's
pre-freeze guard during agent creation, and the composed backend pinned
config and login state for the agent's lifetime against the service's
documented per-call resolution. Both tools now resolve their backend per
invocation, the WebSearch activation gate checks presence alone through the
new hasWebSearchProvider() (no provider composition, no identity read), and
bind() awaits the identity freeze before materializing the model, whose
resolution reads the identity through the host-headers port.
daofazhiran
pushed a commit
to daofazhiran/kimi-code
that referenced
this pull request
Aug 4, 2026
* refactor(agent-core-v2): simplify context tags and shared copy
Rename the context-injection tags to `<skill-loaded>` and
`<plugin-instructions>`, drop the product prefix from the CronCreate tool
description and the default agent description, and point the MCP OAuth
callback page back to "your terminal" instead of naming one client.
The callback page is shared by the ACP host, the web UI, and embedding
hosts, so naming a single client was inaccurate there. The tags and the
two descriptions read exactly the same without the prefix. Verified no
runtime consumer matches the old tag names; the updated snapshots cover
the tool descriptions that changed.
* feat(agent-core-v2): add a switch for the product-documentation skills
Five builtin skills document this CLI itself — `update-config`,
`custom-theme`, `mcp-config`, `check-kimi-code-docs`, and
`import-from-cc-codex`. Their names and descriptions sit in the system
prompt on every turn, which is dead weight for runs that will never
reconfigure the CLI.
Add a top-level `builtin_product_skills` field (also settable through
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS`) to drop them. On by default, so
nothing changes unless it is set; the trade when off is that the model
loses the guided flows for those tasks.
Filtering happens where the catalog is assembled — a later filter would
leave the skills advertised to the model. The whole section is one
scalar, so it exercises the section-level env binding branch and needs
its own strip: `stripEnvBoundFields` only walks object fields, so an env
override would otherwise be written back into `config.toml`.
* feat(agent-core-v2): add custom agent identity
Add an `[identity]` config section (`name`, optional `slug`, both also
settable through `KIMI_CODE_IDENTITY_NAME` / `KIMI_CODE_IDENTITY_SLUG`)
that sets the identity the agent presents: the name it calls itself in
the system prompt, the `User-Agent` product token sent to third-party
providers, and the client name announced to MCP servers. Leaving it
unset changes nothing.
Until now every one of these was fixed, which left no way to run the
agent as part of another product — an internal deployment, a fork with
its own branding, an embedding host.
The identity resolves inside the engine rather than being seeded by each
host, so it applies to every launch surface — including headless runs,
which today seed no display name at all and fall through to the built-in
default.
Two deliberate asymmetries:
- The display name is a filling value with a fallback chain (config >
host-declared > the consumer's own default); the slug is a rewriting
value with two states only, so with no identity configured the
rewriting paths are equivalent to not existing.
- The rewrite happens in the outbound header assembly, the one layer
that knows which vendor it is building for. Vendors declaring
`hostHeaders: 'full'` keep the host's own product token, which that
header set is built around and which backends key on; the configured
identity applies to the third-party path.
Resolution is lazy throughout: config loads asynchronously, and a
constructor snapshot would freeze the pre-load value under some startup
orderings.
Two input edges the resolver has to absorb, since both would otherwise
reach the User-Agent builder and either break it or quietly rewrite the
header: blank and whitespace-only values read as unset in the file just
as they already did in the env, so a stray `name = ""` cannot claim an
identity; and a name that folds away to nothing under slug
normalization (a CJK-only name, say) falls back to a neutral token
rather than producing a blank product, which the builder rejects.
* fix(agent-core-v2): keep the file value when a scalar env binding fails to parse
`config.ts` documents that an env value failing its binding's `parse` is
ignored, and `applyEnvBindings` honors that for object fields by
assigning only when the resolved value is defined. `applySectionEnv`
returned the parse result straight through for whole-section scalar
bindings, so a blank or mistyped variable resolved to `undefined` and
cleared the configured file value instead of being ignored.
Nothing hit this before: every existing section either binds object
fields or is env-only. `builtin_product_skills` is the first
whole-section scalar binding, where exporting an empty or misspelled
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS` would silently undo a configured
`false`.
* feat(agent-core-v2): extend the custom identity to discovery and global MCP
Two outbound paths still announced the built-in product name under a
configured identity:
- `DiscoveryService` read the host User-Agent straight from bootstrap
args when refreshing provider models, so custom registries — which are
third-party endpoints — saw the original token while chat requests to
the same class of endpoint saw the configured one.
- `SDKRpcClientV2` builds its own global `McpOAuthService` plus a
throwaway `McpConnectionManager` for server testing, neither of which
goes through the workspace-owned manager that carries the resolver.
Both now resolve the identity from the App scope.
* refactor(agent-core-v2): neutralize remaining copy and align comments
The synthetic MCP authentication tool description is injected into the
model context and still named the product; it and the OAuth callback
pages now use client-neutral wording. "Return to your terminal" was no
improvement over naming a client — both assume what the host is, and
that page serves the ACP host, the web UI and embedding hosts alike.
Comments introduced by the identity work move into their module headers,
per the domain convention. Interface field docs stay: the rule names
functions, methods and statements, and field-level docs are established
across the codebase.
The new tests gain scenario headers and dispose the scoped hosts they
create, and the `[identity]` docs state which engine reads the section.
* fix(agent-core-v2): read the product-skill switch after config is ready
`BuiltinSkillSource` is the lowest-priority skill source, so the workspace
catalog loads it first — before `IConfigService` has finished loading — and
keeps the contribution it returns for the life of the handler, with no
reload path and no change event. Reading `builtin_product_skills` eagerly
therefore stranded the startup configuration: an explicit `false` could be
ignored for the whole process. `UserFileSkillSource` already awaits config
readiness for exactly this ordering; this source now does the same.
Also record the identity collaborator in the two module headers that gained
the dependency without documenting it, and scope the
`builtin_product_skills` docs to the engine that reads it, matching the
note the identity section already carries.
* fix(agent-core-v2): apply the product-skill switch to session-less listings
`builtin_product_skills = false` only reached the scoped skill source. The
SDK's `listWorkspaceSkills` and the server's `GET /workspaces/{id}/skills`
both composed the raw `BUILTIN_SKILLS` constant, and the web app feeds its
pre-session onboarding menu from that route — so the five product skills
stayed listed until a session existed, then vanished from the session's
catalog.
Move the decision into `visibleBuiltinSkills(enabled)` next to the constant
and route every consumer through it, reading the switch via the shared
`builtinProductSkillsEnabled`. Keeping "what counts as a product skill" in
one place is the point: three copies of the predicate would drift the next
time a builtin is added. The SDK listing also awaits config readiness,
which it did not do before.
* fix(node-sdk): await config before materializing the global MCP OAuth provider
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, and the preceding `globalMcpConfig.get()` reads
`mcp.json` directly rather than through `IConfigService`. So a
`beginGlobalMcpServerAuth` call made right after the harness is created
could resolve the identity before config finished loading, pinning the
built-in label for the rest of the process — including the OAuth dynamic
registration a third-party MCP server records.
`testGlobalMcpServer` already awaited config readiness for its own reasons;
this path now does too.
* refactor(agent-core-v2): drop the unused builtin-skill registrar
`registerBuiltinSkills` stamped the raw constant into a catalog for "edge
composition without a Session" — exactly the shape that now has to respect
`builtin_product_skills`. It has no callers in v2 and is not exported from
the package index, so it was dead code that also stood as an invitation to
bypass the switch. v1 keeps its own copy.
Every remaining path composes builtins through `visibleBuiltinSkills`.
* fix(agent-core-v2): send the configured identity on custom-registry imports
`:import_registry` fetched a user-supplied third-party URL with a
hardcoded `kimi-code-kap-server` User-Agent, so the first request to a
registry announced the product while every scheduled refresh of the same
registry announced the configured identity. The hardcoded value was wrong
on its own terms too: that token names the server, and this path also runs
in the CLI.
Both services now project the identity through `identityUserAgent`, which
carries the two guards (no host header, or no identity) once instead of
per caller. The model catalog keeps an inline copy on purpose — kosong is
a foundational layer and must not import an app domain.
Sweeping the remaining outbound User-Agent sources found no further gaps:
WebFetch deliberately sends a Chrome-like UA, the models.dev catalog fetch
sends none from the CLI, and kap-server's `user-agent` reads are inbound.
* docs: scope the identity env vars and condense the changeset
The environment-variable reference advertised all three new variables
without noting that only the agent-core-v2 engine reads them; the
configuration page already carried that note. Added in both locales.
The changeset had grown into two paragraphs of implementation detail,
which is what would land in the CLI release changelog. `gen-changesets`
asks for one short sentence plus at most a one-line usage hint.
* docs(agent-core-v2): describe the identity as what the agent calls itself
The module headers had drifted into describing the feature by what it
keeps off the wire rather than what it configures. Reworded so they state
the capability: the identity is the name the agent uses for itself, and
the unset case is a no-op rather than something "safe". The product-skill
switch excludes skills rather than hiding them.
Wording only; behavior and structure unchanged.
* test(agent-core-v2): cover the identity on custom-registry imports
The import path switched from a hardcoded `kimi-code-kap-server` token to
the host User-Agent projected through the identity, but nothing asserted
it. Two cases pin both halves: a configured identity reaches the request,
and an unconfigured one leaves the host header intact — the second matters
because a single case would also pass if one hardcoded value had simply
replaced another.
Both fail against the previous implementation.
* fix(node-sdk): guard every global MCP OAuth path behind config readiness
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, so any path that can materialize a provider has
to run after config has loaded. `beginGlobalMcpServerAuth` awaited
readiness, but `resetGlobalMcpServerAuth` reaches the same cache through
`invalidate()` -> `getProvider()` without waiting: resetting auth right
after the harness is constructed pinned the built-in client name, and the
await added to the begin path could not help because it then reused that
cached provider.
Rather than add the missing await, the accessor is now async and holds the
guard itself, so the service cannot be obtained before config is ready and
a future entry point cannot forget. The remaining `configReady` in
`testGlobalMcpServer` stays — that one is for its own `[mcp]` section read.
* fix(agent-core-v2): send the configured identity on models.dev requests
The directory fetch behind `listModelsDevProviders` / `getModelsDevProvider`
still hardcoded a `kimi-code-kap-server` User-Agent, so browsing or importing
from models.dev announced the built-in product — and claimed to be the server
even when running in the CLI. Only the custom-registry import had been fixed.
`getModelsDevCatalog` now takes the User-Agent from its caller: the module is
plain module-level state with no container access, and the value depends on
the host and the configured identity, which only the calling service can see.
All four third-party fetches in that service share one helper.
Where the host states no User-Agent, a neutral token stands in rather than
dropping the header — these are directories the service chooses to call, so
there is no host intent to preserve, unlike the provider requests the model
catalog assembles.
Both new tests fail against the previous hardcoded value.
* test(agent-core-v2): assert the product-skill set literally
The expected sets were derived from the same `productSpecific` field the
production filter reads, so a builtin silently losing its marker would just
move between sets and leave every assertion green — while staying visible to
the model once the switch is off. The five names are now literal, with a test
asserting the marked set matches them exactly.
Dropping the marker from one skill now fails four tests instead of none.
Also states the App scope in the identity contract header, per the domain's
comment convention for contract files.
* fix(agent-core-v2): normalize the host-declared display name too
Blank and padded values were normalized on the config side but not on the
host fallback, so an embedding host passing `displayName: " "` rendered
"You are ," into the system prompt, and a padded name kept its padding.
Same rule now applies to every source of the name.
Also names `agentIdentity` as the collaborator in the request-headers
adapter header, which described the value it obtains without saying which
domain resolves it.
The three new cases fail against the previous implementation.
* fix(agent-core-v2): keep the configured slug when the host sends no User-Agent
The neutral fallback added for hosts that state no `User-Agent` discarded a
configured identity along with it: `identityUserAgent` returns `undefined`
as soon as there is no host header to rewrite, so `?? DEFAULT_IDENTITY_SLUG`
sent the literal `agent` even when `[identity].slug` was set — precisely the
case that fallback exists to serve. The configured slug now stands on its
own, with the neutral token reserved for having neither.
The four combinations of (host header, configured slug) had three tests; the
missing one is the one that was wrong. It now fails without this change.
`outboundUserAgent` also awaits config readiness before reading the identity,
so a browse issued right after bootstrap cannot send the pre-load value — the
guard lives in the accessor rather than at its four call sites, matching how
the same race is handled elsewhere in this branch.
Both headers here and in `discoveryService` now name `agentIdentity` as the
collaborator resolving that token.
* test(acp-server): follow the renamed skill-activation tag
`acp-server` arrived on main after the tag rename, so its two assertions
still expected `kimi-skill-loaded` and failed once the branches met. Also
updates the web app's CSS comment, which named the old tag from the start
of this branch — a comment, so nothing ever failed on it.
Found by CI: the merge verification only ran agent-core-v2's suite, and
this package is neither a dependency nor a dependent of it.
* fix(agent-core-v2): present the configured slug on registry refreshes too
The previous round taught the import path to fall back to the configured
slug when the host states no `User-Agent`, but left the scheduled refresh
of the same registry on the bare projection — so one registry could see
`acme` on import and the runtime default on refresh.
Extracting `identityUserAgent` had made the two paths share a function
without sharing the policy. The choice itself is now the shared piece:
`identityUserAgentOrDefault` always yields a value, for the directories
this process chooses to call, while `identityUserAgent` stays the form
that rewrites only what the host already sends — what a provider request
needs, where the host's silence is its own choice.
* docs(agent-core-v2): move new member docs into the module headers
The domain's comment convention is absolute — comments live solely in the
top-of-file block — and I had read the "functions, methods, or statements"
clause as leaving interface members out. It does not: only 25 of 734 v2
sources carry an indented block, so the members I documented were the
exception, not the pattern.
Seven members across six files move into their headers. `types.ts` had no
header at all, so it gains one.
* fix(agent-core-v2): connect session MCP overlays after config is ready
The shared manager reaches `connectAll` through `initialize()`, which awaits
the config domain first; `sessionOverlay` called it straight away. A session
carrying ephemeral `mcpServers` created right after bootstrap therefore
resolved the client name before config had loaded and initialized under the
built-in one.
The blast radius is wider than that one connection: a remote server sends
the overlay through `hasTokens()`, which materializes an OAuth provider on
the *shared* service and caches it by store key — so the early name outlives
the connection that raced. The overlay now connects behind `mcpConfig.ready`,
leaving the returned readiness promise unchanged.
* fix(agent-core-v2): reload builtin skills when their switch changes
The workspace catalog keeps each source's contribution for the life of the
handler, so a `builtin_product_skills` toggle never reached an existing
handler's sessions. That was harmless while every surface read the same
constant — but routing the session-less listings through the config made the
two views disagree, since those read the switch on every call.
Follows `ExtraFileSkillSource`: subscribe to the owning section and fire
`onDidChange`, which the catalog already turns into a source reload. The
test asserts an unrelated section does not trigger it.
* fix(agent-core-v2): apply the identity to self-configured web services
`[services.moonshot_search]` and `[services.moonshot_fetch]` name their own
`base_url`, so both services can point at an endpoint the user chose — but
each forwarded the host request headers verbatim, sending the built-in
product token there under a configured identity.
Only the services-config path is rewritten; the managed OAuth path keeps the
host headers as they are, being the endpoint the session authenticated
against. The distinction is the same one the model catalog draws per vendor.
`identityHeaders` carries the rewrite across a whole header set, so this is
the fourth caller sharing the projection rather than repeating its guards.
A pair of tests pins both halves.
My earlier sweep classified these two as official by their names instead of
asking who chooses the URL, which is why they were missed. The contract
header is also condensed here, per the convention below.
* docs(agent-core-v2): condense the identity headers to their contracts
The comment convention is one sentence with two halves — comments live only
in the top-of-file block, *and* that block states the module's role without
narrating implementation. Moving the member docs up last round satisfied the
first and broke the second: the headers ended up spelling out the slug
folding algorithm, the strip mechanics, and the load order.
Kept what a caller or the next editor would get wrong without it (why the
value is read rather than snapshotted, what `undefined` obliges a consumer
to do, why this source waits for config). Dropped what the code already
says. 22/12/12/13 lines, against 53 in `catalogService.ts` — length was
never the problem.
* fix(agent-core-v2): rebuild active prompts when the builtin skills change
Reloading the catalog on a `builtin_product_skills` toggle left existing
agents holding the old listing: `AgentProfileService` refreshes the prompt
only for the plugin source, so a disabled switch kept advertising skills
that were gone, and enabling it left them missing until an unrelated
refresh.
The plugin source is special because it also contributes prompt sections
(MoonshotAI#2314), and the file-backed sources are left out for cost — their fs
watches would rebuild every agent's prompt on each edit. The builtin source
has no watch: it changes only when its config switch is toggled, so it
belongs with the plugin source rather than with the file ones.
Subscribing to the catalog rather than the config section is load-bearing.
The catalog fires after the contribution is replaced, whereas a config
subscription would race the reload, and `resolveSkillListing` only awaits
the catalog's *initial* readiness — so the rebuilt prompt could read the
listing it was meant to replace.
The source id is a named constant now, so the subscription does not match
on a bare string.
* refactor(agent-core-v2): freeze the agent identity for the process lifetime
The identity is announced outward (MCP initialize, OAuth registration,
provider request logs) and cannot be re-announced, so mid-process changes
could only ever apply partially. Resolve it once when config first loads
and hold it for the life of the process: IAgentIdentity now hands out a
frozen snapshot via resolved()/current(), carrying finished products
(outbound User-Agent variants, rewritten header set) so call sites stop
composing host headers with the slug themselves. The kosong host-headers
port carries two finished layers and the catalog only picks one; consumers
gain no invalidation obligations because the value can never change after
the freeze. [identity] edits take effect on the next start (documented).
* fix(agent-core-v2): locate the User-Agent header case-insensitively
HTTP header names are case-insensitive, but the snapshot builder looked up
'User-Agent' by exact key: an embedding host spelling it 'user-agent' got no
third-party UA and kept its own product token on the services path even with
an identity configured. The builder now locates every case variant and
rewrites each in place, keeping the host's spelling. Also corrects the two
web-service headers that still described both paths as sending the bootstrap
headers, naming agentIdentity as the collaborator behind the config path,
and documents that a resumed session keeps its recorded system prompt.
* fix(agent-core-v2): attribute header provenance from the finished third-party layer
Inspection reconstructed the non-full host layer from the raw headers with an
exact-case 'User-Agent' lookup, so a host spelling the header 'user-agent'
got a resolved User-Agent with no provenance entry even though the runtime
sends the rewritten value. buildModel now captures the port's finished
third-party layer in the trace and attribution reads it, keeping inspect()
on the same resolution pass as get(). Also condenses the identity contract
header to its external role, and documents that an existing MCP OAuth
authorization keeps the client registration it was granted under (reset the
server's auth to register under the new identity).
* fix(agent-core-v2): keep web tool backends from racing the identity freeze
An env-configured [services] endpoint is visible before config finishes
loading, and FetchURLTool / WebSearchTool materialized their backends at
construction — so a fast bootstrap could hit the identity snapshot's
pre-freeze guard during agent creation, and the composed backend pinned
config and login state for the agent's lifetime against the service's
documented per-call resolution. Both tools now resolve their backend per
invocation, the WebSearch activation gate checks presence alone through the
new hasWebSearchProvider() (no provider composition, no identity read), and
bind() awaits the identity freeze before materializing the model, whose
resolution reads the identity through the host-headers port.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
No linked issue — the problem is explained below.
Problem
Plugins currently have no native way to contribute instructions to the agent's system prompt. Existing manifest capabilities land elsewhere in the conversation:
sessionStartbecomes a user-role<system-reminder>injection,skillInstructionsis added only when one of the plugin's skills is activated, and hooks append user/assistant messages. The skills listing reaches the system prompt, but only as skill names and descriptions. A plugin that needs stable, always-on domain guidance therefore has no dedicated channel.What changed
This PR adds
systemPromptandsystemPromptPathtokimi.plugin.json:systemPromptcontains inline instructions.systemPromptPathpoints to a UTF-8 text file inside the plugin root.systemPromptPathis resolved while parsing the manifest and folded into the resolvedsystemPrompt, so the klient contract only needs the optional resolved string.Enabled contributions are exposed through
IPluginService.enabledSystemPrompts()and rendered with per-plugin provenance annotations plus the same precedence disclaimer used for workspace instruction files. The built-in prompts place a# Plugin Instructionsblock after the skills section. Custom v2 agent files andSYSTEM.mdtemplates can place the same block with${plugin_sections}.The parser limits each inline field and referenced file to 32 KB of UTF-8 content. Prompt assembly accepts at most 64 KB across enabled plugins; contributions beyond that aggregate budget are skipped with a warning. Unsafe paths, blank paths, unreadable files, non-string fields, and oversized content are reported through manifest diagnostics instead of escaping the plugin root or failing unrelated plugins.
Runtime behavior
Both agent engines consume the same manifest fields, diagnostics, provenance format, and budgets, but retain their existing lifecycle models:
kimi weband experimental CLI surfaces): a new Agent builds its prompt from the current App-scope plugin catalog. Explicit plugin reload commits a new catalog snapshot and emitsonDidReload; every live Session then re-pulls its plugin skill source, and after that refresh finishes its Agents request asynchronous prompt rebuilds from the current plugin sections. The v2reloadPlugins()call returns after the catalog commit and does not wait for those per-Session prompt rebuilds. Install, enable, disable, and remove update the App catalog but do not directly refresh existing Agent prompts; a later rebuild, such as compaction or a tool-policy/config refresh, reads the live catalog and may pick up those changes. A restored Agent first replays its persisted prompt unchanged and follows the same live-trigger behavior afterward.kimi -p): Session creation captures the current enabled plugin sections. Explicit plugin reload pushes the refreshed snapshot to every live Session and awaits prompt rebuilds for its ready Agents. Install, enable, disable, and remove without a reload leave live Session snapshots unchanged; new Sessions use the latest catalog.Requests already in flight keep the prompt snapshot with which they started. Toggling an individual plugin MCP server does not change plugin system-prompt sections.
Tests cover manifest parsing and path containment, diagnostics and byte budgets, plugin consumption reads, prompt composition and custom-template variables, v2 source-change prompt rebuild behavior, and legacy live-session refresh behavior. The user documentation covers the manifest fields and engine-specific behavior in English and Chinese.
Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.